/**
 * 
 */
package tileStyle;

import java.util.*;

import tileStyle.Prism.NodeAddress;
import tileStyle.Prism.events.*;
import Prism.core.*;
import Prism.extensions.port.ExtensiblePort;
import Prism.extensions.port.distribution.SocketDistribution;

import java.io.*;

/**
 * @author Yuriy
 *
 */
public class SignInServer extends TileStyleComponent {
	
	public static final long serialVersionUID = 1L;
	
	protected Architecture architecture;
//	protected Scaffold scaffold;
	protected SocketDistribution sd;
	protected FIFOScheduler scheduler;
	protected RRobinDispatcher dispatcher;
	
	protected ExtensiblePort inPort;
	
	protected List<String> hostnames;
	protected List<Port> ports;
	protected int expectedHosts;
	protected boolean ready;
	
	protected boolean set, go;
	protected List<String> setClients, goClients;
	
	
	public SignInServer(String myHostname, int expectedHosts) {
		super("Sign In Server on: " + myHostname);
		hostnames = new ArrayList<String>();
		hostnames.add(myHostname);
		ports = new ArrayList<Port>();
		setClients = new ArrayList<String>();
		setClients.add(myHostname);
		goClients = new ArrayList<String>();
		goClients.add(myHostname);
		this.expectedHosts = expectedHosts;
		ready = (expectedHosts == 1);
		set = ready;
		go = set;
		initializeArchitecture(2600);
	}
	
	private void initializeArchitecture(int portNum) {
		architecture = new Architecture("SignIn Server");
		scheduler = new FIFOScheduler();
		Scaffold scaff = new Scaffold();
		dispatcher = new RRobinDispatcher(scheduler, 10);
		
		scaff.dispatcher = dispatcher;
		scaff.scheduler = scheduler;
		scaffold = scaff;

		architecture.scaffold = scaffold;
		
		inPort = new ExtensiblePort("inPort" + portNum, PrismConstants.REPLY);
		sd = new SocketDistribution(inPort, portNum);
		inPort.addDistributionModule(sd);
		inPort.scaffold = scaffold;
		this.addCompPort(inPort);
		architecture.add(inPort);
		architecture.add(this);
		
		dispatcher.start();
		architecture.start();
	}
	
	public synchronized void handle(Event event) {
		if (TileStyleStarter.DEBUG)
			System.out.println("handling event of type " + event.getClass());
		if (event instanceof SignInEvent) {
			SignInEvent e = (SignInEvent) event;
			String hostname = e.getHostname();
			if (hostnames.contains(hostname))
				throw new TileStyleException("Duplicate IP signin with " + hostname);
			if (hostnames.size() == expectedHosts)
				throw new TileStyleException("Too many hosts tried to signin");
			
			hostnames.add(hostname);
			ExtensiblePort outPort = createOutPort();
			try {
				System.out.println("Server trying to sign into client " + hostname);
				outPort.connect(hostname, 2600);
			}
			catch (IOException e1)  {
				throw new RuntimeException(e1);
			}
			
			ports.add(outPort);
			
			System.out.println("have " + hostnames.size() + " hostnames (expecting: " + expectedHosts);

			if (hostnames.size() == expectedHosts) {
				ready = true;
			}
		}
		else if (event instanceof ClientSetEvent) {
			ClientSetEvent e = (ClientSetEvent) event;
			String hostname = e.getHostname();
			if (setClients.contains(hostname))
				throw new TileStyleException("Duplicate IP set " + hostname);
			if (setClients.size() == expectedHosts)
				throw new TileStyleException("Too many hosts are set");
			
			setClients.add(hostname);
			
			if (setClients.size() == expectedHosts)
				set = true;
		}
		else if (event instanceof ClientGoEvent) {
			ClientGoEvent e = (ClientGoEvent) event;
			String hostname = e.getHostname();
			if (goClients.contains(hostname))
				throw new TileStyleException("Duplicate IP go " + hostname);
			if (goClients.size() == expectedHosts)
				throw new TileStyleException("Too many hosts are go");
			
			goClients.add(hostname);
			
			if (goClients.size() == expectedHosts)
				go = true;
		}
		else if (event instanceof KeepAlive) {
			// do nothing
		}
		else throw new TileStyleException("Unknown event type" + event.getClass());
	}
	
	private ExtensiblePort createOutPort() {
		ExtensiblePort outPort = new ExtensiblePort("outPort", PrismConstants.REQUEST);
		outPort.scaffold = scaffold;
		SocketDistribution sdOut = new SocketDistribution(outPort);   
		outPort.addDistributionModule(sdOut);
		this.addCompPort(outPort);
		architecture.add(outPort);
		outPort.start();
		return outPort;
	}
	
	public List<String> getHostnames() {
		return hostnames;
	}
	
	public boolean isReady() {
		return ready;
	}
	
	public boolean isSet() {
		return set;
	}
	
	public boolean isGo() {
		return go;
	}
	
	public void sendAddressMap(Map<TileType, List<NodeAddress>> addresses) {
		for (int i = 0; i < ports.size(); i++) 
//			send(new AddressMapEvent("Addresses map compiled", new HashMap<TileType, List<NodeAddress>>(addresses)), ports.get(i));
			send(new AddressMapEvent("Addresses map compiled", deepCopyAddresses(addresses)), ports.get(i));
	}
	
	public void tellEveryoneSet() {
		for (int i = 0; i < ports.size(); i++)
			send(new EveryoneSetEvent("Everyone is set"), ports.get(i));
	}
	
	private Map<TileType, List<NodeAddress>> deepCopyAddresses(Map<TileType, List<NodeAddress>> original) {
		HashMap<TileType, List<NodeAddress>> copy = new HashMap<TileType, List<NodeAddress>>();
		for(Iterator<TileType> i = original.keySet().iterator(); i.hasNext();) {
			TileType current = i.next();
			List<NodeAddress> currentList = original.get(current);
			List<NodeAddress> currentListCopy = new ArrayList<NodeAddress>();
			for (Iterator<NodeAddress> j = currentList.iterator(); j.hasNext();) {
				currentListCopy.add(new NodeAddress(j.next()));
			}
			copy.put(new TileType(current), currentListCopy);
		}
		return copy;
	}

}
